package com.ruoyi.charge.protocol;

import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.mybatisflex.core.query.QueryWrapper;
import com.ruoyi.charge.domain.Charger;
import com.ruoyi.charge.domain.ChargerGun;
import com.ruoyi.charge.model.ChargerGunData;
import com.ruoyi.charge.model.ChargingSessionData;
import com.ruoyi.charge.model.ManageWebControllerStartPayload;
import com.ruoyi.charge.service.IChargerGunService;
import com.ruoyi.charge.service.IChargerService;
import com.ruoyi.charge.utils.SerialUtils;
import com.ruoyi.charge.utils.YunCrcUtil;
import com.ruoyi.common.constants.CacheConstant;
import com.ruoyi.common.enums.GunStatusEnum;
import com.ruoyi.common.utils.BcdCodeTransfer;
import com.ruoyi.common.utils.RedisUtil;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.net.NetSocket;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.scheduling.annotation.Async;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
import java.util.Objects;

/**
 * 客户端服务实现类
 *
 * @author laifeng
 * @version 1.0
 * @date 2025/7/16 10:38
 */
@Component
@Slf4j
public class ClientService implements IClientService {
    @Inject
    private IChargerService chargerService;
    @Inject
    private IChargerGunService chargerGunService;
    @Inject
    private Vertx vertx;
    @Inject
    private RedisUtil redisUtil;

    @Override
    @Async
    public void login(NetSocket netSocket, String sn) {
        Charger charger = chargerService.getOne(new QueryWrapper().eq(Charger::getSn, sn));
        if (StrUtil.isEmpty(charger.getSn())) {
            log.error("充电桩SN为空，无法发送登录认证");
            return;
        }

        // 登录认证帧结构: 帧类型(0x01) + SN长度(1B) + SN内容
        Buffer data = Buffer.buffer();
        // 序列号域（2字节）
        data.appendShort(SerialUtils.createSerialNumber(charger.getSn()));
        // 加密标志位
        data.appendByte((byte) 0x00);
        // 功能码
        data.appendByte((byte) 0x01);
        // 桩编码（BCD码，7字节，不足7位补0）
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd(charger.getSn()));
        // 桩类型（1字节，默认0）
        data.appendByte(charger.getType().byteValue());
        // 充电枪数量（1字节，默认1）
        data.appendByte(charger.getGunNum().byteValue());
        // 通信协议版本（1字节，默认0x0A表示V1.0）
        data.appendByte((byte) 0x01);
        // 程序版本 ASCII 码 8 不足 8 位补零
        data.appendBytes(StrUtil.padAfter("v1.0.1", 8, '0').getBytes(StandardCharsets.US_ASCII));
        // 网络链接类型（1字节，默认0x00表示SIM卡）
        data.appendByte((byte) 0x01);
        // Sim卡（BCD码，10字节，不足10位补零）
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd(StrUtil.padAfter("1", 20, '0')));
        // 运营商（1字节，默认0x04表示其他）
        data.appendByte((byte) 0x04);

        byte[] crc = YunCrcUtil.calculateCrc(data.getBytes());

        // 调用公共方法发送数据
        sendData(netSocket, data, crc);
    }

    @Override
    @Async
    public void ping(NetSocket netSocket, String sn) {
        List<ChargerGun> list = chargerGunService.getChargerGunListCacheBySn(sn);
        for (ChargerGun chargerGun : list) {
            // 68（起始标志）0D（数据长度）0001（序列号域）00（加密标志）03（类型）32010200000001（桩编码）0x01（枪号：1 号枪）00（枪状态：正常）6890（帧校验域）
            Buffer data = Buffer.buffer();
            data.appendShort(SerialUtils.createSerialNumber(chargerGun.getChargerSn()));
            data.appendByte((byte) 0x00);
            data.appendByte((byte) 0x03);
            data.appendBytes(BcdCodeTransfer.decimalStringToBcd(sn));
            data.appendBytes(BcdCodeTransfer.decimalStringToBcd(StrUtil.toString(chargerGun.getNumber())));
            data.appendByte((byte) 0x00);
            byte[] crc = YunCrcUtil.calculateCrc(data.getBytes());
            // 调用公共方法发送数据
            sendData(netSocket, data, crc);
        }
    }


    @Override
    public void requestChargingModel(NetSocket netSocket, String sn) {
        // 68（起始标志）0D（数据长度）0001（序列号域）00（加密标志）03（类型）32010200000001（桩编码）0x01（枪号：1 号枪）00（枪状态：正常）6890（帧校验域）
        Buffer data = Buffer.buffer();
        data.appendShort(SerialUtils.createSerialNumber(sn));
        data.appendByte((byte) 0x00);
        data.appendByte((byte) 0x05);
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd(sn));
        data.appendShort(SerialUtils.createFeeModelSerialNumber(sn));
        byte[] crc = YunCrcUtil.calculateCrc(data.getBytes());
        sendData(netSocket, data, crc);
    }

    @Override
    @Async
    public void requestChargerChargingModel(NetSocket netSocket, String sn) {
        // 68（起始标志）0B（数据长度）0200（序列号域）00（加密标志）09（类型）55031412782305（桩编码）DD25（帧校验域）
        Buffer data = Buffer.buffer();
        data.appendShort(SerialUtils.createSerialNumber(sn));
        data.appendByte((byte) 0x00);
        data.appendByte((byte) 0x09);
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd(sn));
        byte[] crc = YunCrcUtil.calculateCrc(data.getBytes());
        sendData(netSocket, data, crc);
    }

    @Override
    public void uploadRealTimeData(NetSocket netSocket, String ChargerSn) {

    }

    @Override
    public void uploadRealTimeData(NetSocket netSocket, ChargerGun ChargerGun) {
        String chargerSn = ChargerGun.getChargerSn();
        Integer gunStatus = ChargerGun.getGunStatus();
        if (GunStatusEnum.IDLE.getCode().equals(gunStatus) && ChargerGun.getIsInsert().equals(1)) {
            // 说明刚插枪 上报充电枪状态
            // 68 （起始标志） 40 （数据长度） 1A03 （序列号域） 00 （加密标志） 13 （类型）00000000000000000000000000000000（交易流水号）55031412782305（桩编码）
            // 02（枪号：1 号枪）00（状态：离线）01（是否归位：已归位）01（是否插枪：是）0200（输出电压：0）0000（输出电流：0）00（枪线温度：10）
            // 0000000000000000（枪线编码）00（soc：0）00（电池组最高温度：0）00 00（累计充电时间：0）00 00（剩余时间：0）
            // 00 00 00 00（充电度数：0）00 00 00 00（计损充电度数：0）00 00 00 00（已充金额：0）0000（硬件故障：无）9DAC（帧校验域）

            Pair<Buffer, byte[]> pair = getStatusData(ChargerGun, chargerSn);
            Buffer data = pair.getKey();
            byte[] crc = pair.getValue();
            // 调用公共方法发送数据
            sendData(netSocket, data, crc);
        } else {
            // 说明开始充电
            Pair<Buffer, byte[]> pair = getChargingData(ChargerGun, chargerSn);
            Buffer data = pair.getKey();
            byte[] crc = pair.getValue();
            sendData(netSocket, data, crc);
        }
    }

    /**
     * 充电桩状态数据
     *
     * @param chargerGun
     * @param chargerSn
     * @return
     */
    private Pair<Buffer, byte[]> getStatusData(ChargerGun chargerGun, String chargerSn) {
        Buffer data = Buffer.buffer();
        // 序列号域（2字节）
        data.appendShort(SerialUtils.createSerialNumber(chargerSn));
        // 加密标志（1字节）
        data.appendByte((byte) 0x00);
        // 功能码（1字节）
        data.appendByte((byte) 0x13);

        // 交易流水号（16字节BCD码）
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd("00000000000000000000000000000000"));

        // 桩编号（7字节BCD码）
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd(StrUtil.padPre(chargerSn, 14, '0')));

        // 枪号（1字节BCD码）
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd(String.valueOf(chargerGun.getNumber())));

        // 状态（1字节BIN码）
        data.appendByte(GunStatusEnum.IDLE.getCode().byteValue());

        // 枪是否归位（1字节BIN码）
        data.appendByte((byte) 0x00);

        // 是否插枪（1字节BIN码）
        data.appendByte((byte) 0x01);

        // 输出电压（2字节BIN码）
        data.appendShort((short) 0);

        // 输出电流（2字节BIN码）
        data.appendShort((short) 0);

        // 枪线温度（1字节BIN码）
        data.appendByte((byte) 10);

        // 枪线编码（8字节BIN码）
        data.appendBytes(new byte[8]);

        // SOC（1字节BIN码）
        data.appendByte((byte) 0);

        // 电池组最高温度（1字节BIN码）
        data.appendByte((byte) 0);

        // 累计充电时间（2字节BIN码）
        data.appendShort((short) 0);

        // 剩余时间（2字节BIN码）
        data.appendShort((short) 0);

        // 充电度数（4字节BIN码）
        data.appendInt(0);

        // 计损充电度数（4字节BIN码）
        data.appendInt(0);

        // 已充金额（4字节BIN码）
        data.appendInt(0);

        // 硬件故障（2字节BIN码）
        data.appendShort((short) 0);

        byte[] crc = YunCrcUtil.calculateCrc(data.getBytes());
        return Pair.of(data, crc);
    }

    /**
     * 获取充电实时数据
     */
    private Pair<Buffer, byte[]> getChargingData(ChargerGun chargerGun, String chargerSn) {
        String redisKey = CacheConstant.getChargingSession(chargerSn, chargerGun.getNumber());
        ChargingSessionData sessionData = redisUtil.getCacheObject(redisKey);
        long now = System.currentTimeMillis();
        String transactionIdKey = CacheConstant.getTransactionId(chargerSn, chargerGun.getNumber());
        String transactionId = StrUtil.toString(redisUtil.getCacheObject(transactionIdKey));

        // 获取或创建 ChargerGunData 实例（建议从缓存或状态管理中获取，避免每次都 new）
        ChargerGunData gunData = redisUtil.getCacheObject(CacheConstant.getChargerGunData(chargerSn, chargerGun.getNumber()));
        if (gunData == null) {
            gunData = new ChargerGunData();
            gunData.setNumber(Integer.parseInt(chargerGun.getNumber()));
            redisUtil.setCacheObject(CacheConstant.getChargerGunData(chargerSn, chargerGun.getNumber()), gunData);
        }

        // 如果正在充电，更新动态数据（SOC、remainingTime 等）
        if (Objects.equals(chargerGun.getGunStatus(), GunStatusEnum.CHARGING.getCode())) {
            if (gunData.isCharging()) {
                // 计算距离上次更新的时间差
                long lastUpdateTime = gunData.getLastUpdateTime(); // 你需要在 ChargerGunData 加一个字段
                long deltaTime = now - lastUpdateTime;
                gunData.updateForCharging(deltaTime);
            } else {
                // 刚开始充电
                gunData.startCharging();
            }
        } else {
            // 非充电状态，停止
            gunData.stopCharging();
        }

        // 更新最后更新时间（需在 ChargerGunData 中添加字段）
        gunData.setLastUpdateTime(now);

        // 核心逻辑：判断当前是否处于充电状态
        if (Objects.equals(chargerGun.getGunStatus(), GunStatusEnum.CHARGING.getCode())) {
            if (sessionData == null) {
                sessionData = new ChargingSessionData();
                sessionData.setTransactionId(transactionId);
                sessionData.setStartTime(now);
                sessionData.setLastReportTime(now);
                sessionData.setAccumulatedEnergyKwh(BigDecimal.ZERO);
                sessionData.setAccumulatedCost(BigDecimal.ZERO);
            } else {
                long timeDeltaSeconds = (now - sessionData.getLastReportTime()) / 1000;
                if (timeDeltaSeconds > 0) {
                    BigDecimal powerKw = BigDecimal.valueOf(gunData.getOutputVoltage())
                            .multiply(BigDecimal.valueOf(gunData.getOutputCurrent()))
                            .divide(BigDecimal.valueOf(1000), 5, RoundingMode.HALF_UP);

                    BigDecimal energyAddedKwh = powerKw.multiply(BigDecimal.valueOf(timeDeltaSeconds))
                            .divide(BigDecimal.valueOf(3600), 5, RoundingMode.HALF_UP);

                    sessionData.setAccumulatedEnergyKwh(sessionData.getAccumulatedEnergyKwh().add(energyAddedKwh));

                    BigDecimal pricePerKwh = new BigDecimal("1.2");
                    BigDecimal costAdded = energyAddedKwh.multiply(pricePerKwh);
                    sessionData.setAccumulatedCost(sessionData.getAccumulatedCost().add(costAdded));
                }
                sessionData.setLastReportTime(now);
            }
            redisUtil.setCacheObject(redisKey, sessionData, Duration.ofMinutes(5));
        }

        // --- 组装数据包 ---
        Buffer data = Buffer.buffer();
        data.appendShort(SerialUtils.createSerialNumber(chargerSn));
        data.appendByte((byte) 0x00);
        data.appendByte((byte) 0x13);
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd(transactionId));
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd(StrUtil.padPre(chargerSn, 14, '0')));
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd(StrUtil.padPre(chargerGun.getNumber(), 2, '0')));
        data.appendByte(gunData.getStatus().getCode().byteValue());
        data.appendByte((byte) (gunData.isHolstered() ? 0x01 : 0x00));
        data.appendByte((byte) (gunData.isConnected() ? 0x01 : 0x00));

        short voltage = (short) (gunData.getOutputVoltage() * 10);
        data.appendShort(voltage);

        short current = (short) (gunData.getOutputCurrent() * 10);
        data.appendShort(current);

        byte cableTemp = (byte) (gunData.getCableTemperature() + 50);
        data.appendByte(cableTemp);

        data.appendBytes(new byte[8]); // 枪线编码

        data.appendByte((byte) gunData.getSoc());

        byte batteryTemp = (byte) (gunData.getBatteryMaxTemperature() + 50);
        data.appendByte(batteryTemp);

        int accumulatedMinutes = 0;
        if (sessionData != null) {
            accumulatedMinutes = (int) ((now - sessionData.getStartTime()) / (1000 * 60));
        }
        data.appendShort((short) accumulatedMinutes);

        data.appendShort((short) gunData.getRemainingTime());

        int energyScaled = 0;
        if (sessionData != null) {
            energyScaled = sessionData.getAccumulatedEnergyKwh().multiply(BigDecimal.valueOf(10000)).intValue();
        }
        data.appendInt(energyScaled);
        data.appendInt(energyScaled);

        int costScaled = 0;
        if (sessionData != null) {
            costScaled = sessionData.getAccumulatedCost().multiply(BigDecimal.valueOf(10000)).intValue();
        }
        data.appendInt(costScaled);

        data.appendShort(gunData.getHardwareFault());

        byte[] crc = YunCrcUtil.calculateCrc(data.getBytes());
        return Pair.of(data, crc);
    }

    @Override
    public void remoteChargeStartReply(NetSocket netSocket, ManageWebControllerStartPayload payload) {
        // 68（起始标志）1E（数据长度）0002（序列号域）00（加密标志）33（类型）
        //32010200000000111511161555350260（交易流水号）32010200000001（桩编码）01（枪号：1
        //枪）01（启动结果：成功）00（失败原因：无）0FE2（帧校验域）
        Buffer data = Buffer.buffer();
        data.appendShort(SerialUtils.createSerialNumber(payload.getSn()));
        data.appendByte((byte) 0x00);
        data.appendByte((byte) 0x33);
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd(payload.getTradeSerialNumber()));
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd(payload.getSn()));
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd(payload.getGunNo()));
        data.appendByte((byte) 0x00);
        byte[] crc = YunCrcUtil.calculateCrc(data.getBytes());
        sendData(netSocket, data, crc);
        log.info("发送远程启动充电成功指令");

    }

    @Override
    public void chargeHandshake(NetSocket netSocket, String ChargerSn) {

    }

    @Override
    public void parameterConfiguration(NetSocket netSocket, String ChargerSn) {

    }

    @Override
    @Async
    public void chargeEnd(NetSocket netSocket, String chargerSn, String gunNo) {
        log.info("开始发送远程结束充电指令");
        // 68 （ 起 始 标 志 ） 2B （ 数 据 长 度 ） 0016 （ 序 列 号 域 ） 00 （ 加 密 标 志 ） 19 （ 类 型 ）
        //3201020000000011151116155535026（交易流水号）32010200000001（桩编码）01（枪号）00(BMS
        //中止荷电状态 SOC)0000(BMS 动力蓄电池单体最低电压) 0000 (BMS 动力蓄电池单体最高电压)
        //00(BMS 动力蓄电池最低温度) 00(BMS 动力蓄电池最高温度) 0000(电桩累计充电时间) 0000(电
        //桩输出能量)00000000(电桩充电机编号) AE36（帧校验域）

        log.info("开始发送远程结束充电指令，充电桩：{}，枪号：{}", chargerSn, gunNo);

        // 1. 获取枪对象
        ChargerGun chargerGun = chargerGunService.getChargerGunCacheBySnAndNumber(chargerSn, gunNo);
        if (chargerGun == null) {
            log.warn("未找到充电桩枪信息，SN={}, gunNo={}", chargerSn, gunNo);
            return;
        }

        // 2. 构建缓存 key
        String sessionKey = CacheConstant.getChargingSession(chargerSn, gunNo); // 如 "charging:session:SN:01"
        String transactionIdKey = CacheConstant.getTransactionId(chargerSn, gunNo);
        String gunDataKey = CacheConstant.getChargerGunData(chargerSn, gunNo);

        // 3. 获取会话数据和枪数据
        ChargingSessionData sessionData = redisUtil.getCacheObject(sessionKey);
        ChargerGunData gunData = redisUtil.getCacheObject(gunDataKey);

        if (sessionData == null) {
            log.warn("充电会话数据为空，无法发送结束报文，SN={}, gunNo={}", chargerSn, gunNo);
            return;
        }

        String transactionId = StrUtil.toString(redisUtil.getCacheObject(transactionIdKey));
        transactionId = StrUtil.padPre(transactionId, 32, '0'); // 补足32位

        // 4. 开始组包
        Buffer data = Buffer.buffer();

        // 4.1 序列号域（2字节）
        data.appendShort(SerialUtils.createSerialNumber(chargerSn));
        // 4.2 加密标志
        data.appendByte((byte) 0x00);
        // 4.3 功能码 0x19
        data.appendByte((byte) 0x19);

        // 4.4 交易流水号（16字节 BCD）
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd(transactionId));

        // 4.5 桩编号（7字节 BCD）
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd(StrUtil.padPre(chargerSn, 14, '0')));

        // 4.6 枪号（1字节 BCD）
        data.appendBytes(BcdCodeTransfer.decimalStringToBcd(StrUtil.padPre(gunNo, 2, '0')));

        // 4.7 BMS 中止 SOC（1字节 BIN，0~100）
        int soc = gunData != null ? Math.max(0, Math.min(100, gunData.getSoc())) : 0;
        data.appendByte((byte) soc);

        // 4.8 BMS 单体最低电压（2字节 BIN，0.01V）
        int minVoltage = RandomUtil.randomInt(23); // 随机电压 0-23v
        data.appendShort((short) minVoltage);

        // 4.9 BMS 单体最高电压（2字节 BIN，0.01V）
        int maxVoltage = RandomUtil.randomInt(minVoltage, 24); // 0-24v
        data.appendShort((short) maxVoltage);

        // 4.10 BMS 最低温度（1字节 BIN，偏移 -50）
        int minTemp = RandomUtil.randomInt(100);
        data.appendByte((byte) (minTemp));

        // 4.11 BMS 最高温度（1字节 BIN，偏移 -50）
        int maxTemp = RandomUtil.randomInt(minTemp, 200);
        data.appendByte((byte) (maxTemp ));

        // 4.12 电桩累计充电时间（2字节 BIN，单位：min）
        long startTime = sessionData.getStartTime();
        int chargeMinutes = (int) ((System.currentTimeMillis() - startTime) / (60 * 1000));
        chargeMinutes = Math.min(600, chargeMinutes); // 最大600分钟
        data.appendShort((short) chargeMinutes);

        // 4.13 电桩输出能量（2字节 BIN，单位：0.1kWh）
        BigDecimal totalEnergyKwh = sessionData.getAccumulatedEnergyKwh();
        int energy10kWh = totalEnergyKwh.multiply(BigDecimal.valueOf(10)).intValue(); // 转为 0.1kWh
        energy10kWh = Math.min(10000, energy10kWh); // 最大1000kWh → 10000 * 0.1kWh
        data.appendShort((short) energy10kWh);

        // 4.14 电桩充电机编号（4字节 BIN）
        // 可从配置或 gunData 中获取，这里假设为 1
        int machineId = 1;
        data.appendInt(machineId);

        // 5. 计算 CRC
        byte[] rawData = data.getBytes();
        byte[] crc = YunCrcUtil.calculateCrc(rawData);

        sendData(netSocket, data, crc);


    }


    /**
     * 公共方法：发送数据
     *
     * @param netSocket NetSocket 对象
     * @param data      数据缓冲区
     * @param crc       CRC 校验值
     */
    private void sendData(NetSocket netSocket, Buffer data, byte[] crc) {
        // 发送数据
        Buffer buffer = Buffer.buffer();
        buffer.appendByte((byte) 0x68);
        buffer.appendByte((byte) data.getBytes().length);
        buffer.appendBytes(data.getBytes());
        buffer.appendBytes(crc);
        netSocket.write(buffer);
    }


}
